package datapack

import (
	"bytes"
	"crypto/sha1"
	"encoding/binary"
	"fmt"
	"gitee.com/samhula/quote-common/qoutepb/InitConnect"
	"gitee.com/samhula/quote-common/qoutepb/KeepAlive"
	"gitee.com/samhula/quote-common/qoutepb/Msgid"
	"gitee.com/samhula/quote-common/qoutepb/Qot_GetBasicQot"
	"gitee.com/samhula/quote-common/qoutepb/Qot_GetOrderBook"
	"gitee.com/samhula/quote-common/qoutepb/Qot_GetSnapshotQot"
	"gitee.com/samhula/quote-common/qoutepb/Qot_GetTickData"
	"gitee.com/samhula/quote-common/qoutepb/Qot_GetTimeShare"
	"gitee.com/samhula/quote-common/qoutepb/Qot_GetTinyTimeShare"
	"gitee.com/samhula/quote-common/qoutepb/Qot_PreAfterMarketData"
	"gitee.com/samhula/quote-common/qoutepb/Qot_Sub"
	"gitee.com/samhula/quote-common/qoutepb/Qot_UpdateBasicQot"
	"gitee.com/samhula/quote-common/qoutepb/Qot_UpdateOrderBook"
	"gitee.com/samhula/quote-common/qoutepb/Qot_UpdateSnapshotQot"
	"gitee.com/samhula/quote-common/qoutepb/Qot_UpdateTickData"
	"github.com/golang/protobuf/proto"
	"sync/atomic"
	"time"
)

const minFrameSize int = 42

type handler func([]byte) (proto.Message, error)

var msgIdHandlerMap = map[Msgid.YLProtoMsgId]handler{
	Msgid.YLProtoMsgId_MSGID_INIT_CONNECT: func(b []byte) (proto.Message, error) {
		var resp InitConnect.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_KEEP_ALIVE: func(b []byte) (proto.Message, error) {
		var resp KeepAlive.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_QT_COMMAND_SUB: func(b []byte) (proto.Message, error) {
		var resp Qot_Sub.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_COMMAND_GetBasicQot: func(b []byte) (proto.Message, error) {
		var resp Qot_GetBasicQot.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_COMMAND_GetSnapshotQot: func(b []byte) (proto.Message, error) {
		var resp Qot_GetSnapshotQot.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_COMMAND_GetTimeShare: func(b []byte) (proto.Message, error) {
		var resp Qot_GetTimeShare.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_COMMAND_GetTinyTimeShare: func(b []byte) (proto.Message, error) {
		var resp Qot_GetTinyTimeShare.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_COMMAND_GetOrderBook: func(b []byte) (proto.Message, error) {
		var resp Qot_GetOrderBook.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_COMMAND_GetTick: func(b []byte) (proto.Message, error) {
		var resp Qot_GetTickData.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_QT_DATA_UpdateSnapshotQot: func(b []byte) (proto.Message, error) {
		var resp Qot_UpdateSnapshotQot.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_QT_DATA_UpdateBasicQot: func(b []byte) (proto.Message, error) {
		var resp Qot_UpdateBasicQot.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_DATA_UpdateOrderBook: func(b []byte) (proto.Message, error) {
		var resp Qot_UpdateOrderBook.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_DATA_UpdatePreAfterMarket: func(b []byte) (proto.Message, error) {
		var resp Qot_PreAfterMarketData.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
	Msgid.YLProtoMsgId_MSGID_DATA_UpdateTick: func(b []byte) (proto.Message, error) {
		var resp Qot_UpdateTickData.Response
		err := proto.Unmarshal(b, &resp)
		return &resp, err
	},
}

type C2SProtoHeader struct {
	ProtoID         uint16
	ProtoFormatType uint16
	ProtoVersion    uint16
	PacketSeqNum    uint32
	BodyLength      uint32
	BodySha1        [20]byte
	Reserve         uint64
	BodyData        []byte
	RespBodyMsg     proto.Message `json:"-"`
}

var _startPacketSeqNum uint32 = 0

func NewC2SHeader(msgId Msgid.YLProtoMsgId) *C2SProtoHeader {
	packetSeqNum := atomic.AddUint32(&_startPacketSeqNum, 1)
	return &C2SProtoHeader{
		ProtoID:         uint16(msgId),
		ProtoFormatType: 0,
		ProtoVersion:    0,
		PacketSeqNum:    packetSeqNum,
		Reserve:         uint64(time.Now().UnixMicro()),
	}
}

func MsgToC2SHeader(b []byte) (*C2SProtoHeader, error) {
	header := &C2SProtoHeader{}
	msg, err := header.ToStruct(b)
	if err != nil {
		return header, err
	}
	header.RespBodyMsg = msg
	return header, nil
}

func (c *C2SProtoHeader) toBinary() []byte {
	buffer := bytes.NewBuffer([]byte{})
	binary.Write(buffer, binary.BigEndian, c.ProtoID)
	binary.Write(buffer, binary.BigEndian, c.ProtoFormatType)
	binary.Write(buffer, binary.BigEndian, c.ProtoVersion)
	binary.Write(buffer, binary.BigEndian, c.PacketSeqNum)
	binary.Write(buffer, binary.BigEndian, c.BodyLength)
	binary.Write(buffer, binary.BigEndian, c.BodySha1)
	binary.Write(buffer, binary.BigEndian, c.Reserve)
	buf := append(buffer.Bytes(), c.BodyData...)

	return buf
}

func (c *C2SProtoHeader) EncodeWithPb(pb proto.Message) *C2SProtoHeader {
	data, err := proto.Marshal(pb)
	if err != nil {
		return nil
	}
	header := &C2SProtoHeader{
		ProtoID:         c.ProtoID,
		ProtoVersion:    c.ProtoVersion,
		ProtoFormatType: c.ProtoFormatType,
		PacketSeqNum:    c.PacketSeqNum,
		Reserve:         c.Reserve,
		BodyData:        data,
		BodyLength:      uint32(len(data)),
		BodySha1:        sha1.Sum(data),
	}
	return header
}

func (c *C2SProtoHeader) ToBinaryWithPb(pb proto.Message) []byte {
	data, err := proto.Marshal(pb)
	if err != nil {
		return nil
	}
	c.BodyData = data
	c.BodyLength = uint32(len(data))
	c.BodySha1 = sha1.Sum(data)
	return c.toBinary()
}

func (c *C2SProtoHeader) ToStruct(b []byte) (proto.Message, error) {
	if len(b) < minFrameSize {
		return nil, fmt.Errorf("data len < 42")
	}
	c.ProtoID = binary.BigEndian.Uint16(b[0:2])
	c.ProtoFormatType = binary.BigEndian.Uint16(b[2:4])
	c.ProtoVersion = binary.BigEndian.Uint16(b[4:6])
	c.PacketSeqNum = binary.BigEndian.Uint32(b[6:10])
	c.BodyLength = binary.BigEndian.Uint32(b[10:14])
	buf := b[14:34]
	for i, f := range buf {
		c.BodySha1[i] = f
	}
	c.Reserve = binary.BigEndian.Uint64(b[34:42])
	c.BodyData = b[42:]
	if f, ok := msgIdHandlerMap[Msgid.YLProtoMsgId(c.ProtoID)]; ok {
		msg, err := f(c.BodyData)
		if err != nil {
			return nil, fmt.Errorf("protoId:%d err:%s", c.ProtoID, err.Error())
		}
		return msg, nil
	}
	return nil, fmt.Errorf("handler miss protoId:%d", c.ProtoID)
}
